#!/usr/bin/env python3
import argparse
import os
import plistlib
import re
from subprocess import check_call
import sys


SOURCE_ROOT = os.path.realpath(os.path.join(__file__, '..', '..'))
os.chdir(SOURCE_ROOT)


INFOPLIST_PATH = os.path.join(SOURCE_ROOT, 'Framework', 'Info.plist')


def eprint(*args, **kwargs):
    print(*args, file=sys.stderr, **kwargs)


class Version(object):
    def __init__(self, major, minor, patch, bundle):
        super(Version, self).__init__()

        (self.major, self.minor, self.patch) = (major, minor, patch)
        self.bundle = bundle

    @classmethod
    def from_info_plist(cls, plist):
        (string, bundle) = (plist['CFBundleShortVersionString'], plist['CFBundleVersion'])

        parts = [int(s) for s in string.split('.')]
        while len(parts) < 3:
            parts.append(0)

        return cls(parts[0], parts[1], parts[2], int(bundle))

    def increment_major(self):
        return self.__class__(self.major + 1, 0, 0, self.bundle + 1)

    def increment_minor(self):
        return self.__class__(self.major, self.minor + 1, 0, self.bundle + 1)

    def increment_patch(self):
        return self.__class__(self.major, self.minor, self.patch + 1, self.bundle + 1)

    def __str__(self):
        if self.patch > 0:
            return f'{self.major}.{self.minor}.{self.patch}'
        else:
            return f'{self.major}.{self.minor}'


def get_current_version():
    with open(INFOPLIST_PATH, 'rb') as fp:
        plist = plistlib.load(fp)
    return Version.from_info_plist(plist)


def increment_version(old_version, part):
    if part == 'major':
        return old_version.increment_major()
    elif part == 'minor':
        return old_version.increment_minor()
    elif part == 'patch':
        return old_version.increment_patch()
    else:
        raise ValueError(u"Can only increment a version's major, minor, or patch parts")


def update_change_log(next_version):
    path = os.path.join(SOURCE_ROOT, 'CHANGELOG.md')
    with open(path, 'r+') as changelog:
        updated = changelog.read()

        def append_header(match):
            unreleased = match.group(0)
            return f"{unreleased}\n## [{next_version}][]\n"

        updated = re.sub(r'^##\s*\[Unreleased\]\s*$', append_header, updated, flags=re.MULTILINE)

        def adjust_links(match):
            (base_url, old_version) = (match.group(1), match.group(2))
            return f"[Unreleased]: {base_url}/v{next_version}...HEAD\n" \
                   f"[{next_version}]: {base_url}/v{old_version}...v{next_version}"

        updated = re.sub(r'^\[Unreleased\]:\s*(\S+?)/v([0-9.]+?)\.{3}.*$',
                         adjust_links, updated, flags=re.MULTILINE)

        changelog.seek(0)
        changelog.write(updated)
        changelog.truncate()


def commit_changes(next_version):
    check_call(["git", "add", "--", "CHANGELOG.md", "Framework/Info.plist"])
    commit_message = f"Bump version to {next_version} ({next_version.bundle})."
    check_call(["git", "commit", "-m", commit_message])


def git_tag(next_version, prefix=""):
    tag_message = f"HTMLReader {next_version}"
    version = f"{prefix}{next_version}"
    check_call(["git", "tag", "-am", tag_message, version])


def update_info_plist(next_version):
    with open(INFOPLIST_PATH, 'rb') as fp:
        plist = plistlib.load(fp)
    plist['CFBundleShortVersionString'] = str(next_version)
    plist['CFBundleVersion'] = str(next_version.bundle)
    with open(INFOPLIST_PATH, 'wb') as fp:
        plistlib.dump(plist, fp)


def main():
    parser = argparse.ArgumentParser(description="Increment the library version")
    subparsers = parser.add_subparsers(dest='part', help="Commands")
    subparsers.add_parser('major', help="Bump major version")
    subparsers.add_parser('minor', help="Bump minor version")
    subparsers.add_parser('patch', help="Bump patch version")
    part = parser.parse_args().part

    old_version = get_current_version()
    next_version = increment_version(old_version, part)

    print(f"{old_version} -> {next_version}")

    update_change_log(next_version)
    update_info_plist(next_version)
    commit_changes(next_version)
    git_tag(next_version)
    git_tag(next_version, prefix="v")


if __name__ == '__main__':
    main()
